﻿/*
 * TIFFTag
 * 
 * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
 * All rights reserved.
 * 
 * C# translation and update 
 * Copyright (c) Adam Sida, asida@centrum.cz
 * 
 * All rights reserved.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;


namespace Petra.Gis.Geometry.Image.Tiff
{

  ///<summary>
  /// represents the data stored in a TIFF <class>Tag</class> (a single image file directory entry).
  /// That includes the following items:
  /// identification number, 254 or higher; see the many TAG_xyz constants in
  /// {@link TIFFConstants} for a list of allowed values
  /// type; the allowed types include integer and floating point numbers, Strings etc.;
  /// see the TAG_TYPE_xyz constants in {@link TIFFConstants}
  /// count; the number of values of the given type that are stored in this tag;
  /// 1 or higher
  /// offset; if count is 1 and the type fits into four bytes, this is the complete
  ///  value of this tag; otherwise, it is an offset into the file to the position
  ///  that will hold the value(s)
  ///
  ///See the TIFF specification manual linked in the description of {@link TIFFCodec}
  ///for more details.
  ///</summary>
public partial class Tag
{
	private TagId _id;
  private int _tagNumber;
	private TagDataType _type;
	private int _count;
	private int _offset;
	private ArrayList _tagData;
  private ImageFileDirectory _ifd;


  ///<summary>
  /// Creates a new tag from input binary stream
  /// The additional data is somewhere in the TIFF file.
  /// The current position will be stored, the method can seek to the offset
  /// position and load the data.
  ///</summary>
  internal Tag(ImageFileDirectory ifd, TiffReader tiffReader)
  {
    
    _tagNumber = (int)tiffReader.ReadUInt16();
    try { _id = (TagId)_tagNumber; }
    catch { _id = TagId.UNKNOWN; }

    TagDataType type = (TagDataType)tiffReader.ReadInt16();
    int count = tiffReader.ReadInt32();
    int offset = tiffReader.ReadInt32();

    if (count < 1) //empty tag, no values
    {
      //throw new InvalidFileStructureException("Invalid count value for tag " + id + " (" + count + ").");
      return;
    }

    // ArrayList vector = null;
    // perform weird bitshifting magic if necessary
    long oldOffset;
    if ((count == 1 && (type == TagDataType.BYTE ||
                       type == TagDataType.ASCII ||
                       type == TagDataType.SHORT ||
                       type == TagDataType.LONG ||
                       type == TagDataType.SBYTE ||
                       type == TagDataType.SSHORT ||
                       type == TagDataType.SLONG)) ||
        (count <= 4 && (type == TagDataType.BYTE ||
                       type == TagDataType.ASCII ||
                       type == TagDataType.SBYTE)) ||
        (count <= 2 && (type == TagDataType.SHORT ||
                        type == TagDataType.SSHORT))

        )
    {
      //read data direcly no pointer
      //offset = adjustInt(offset, type);
      oldOffset = -1;
    }
    else
    {
      //use pointer to read data
      oldOffset = tiffReader.BaseStream.Position;
      tiffReader.BaseStream.Seek(offset, SeekOrigin.Begin);
    }

    ArrayList vector = new ArrayList();

    //prepare array for string
    byte[] strArr;
    if(type == TagDataType.ASCII)
      strArr = new byte[count];
    else
      strArr = new byte[1]; //fucking compilator, dont remove this !!!

    //StringBuilder sb = new StringBuilder(count);

    if (oldOffset > 0) //if data outside of table, read them into vector
    {
      for (int i = 0; i < count; i++)
      {
        switch (type)
        {
          case TagDataType.BYTE:
          case TagDataType.SBYTE:
            vector.Add(tiffReader.ReadByte()); //no endian conversion necessary for byte ;-)
            break;
          case TagDataType.ASCII:
            //sb.Append(tiffReader.ReadChar());//readString(count); //no endian conversion necessary for byte ;-)
            strArr[i] = tiffReader.ReadByte();
            break;
          case TagDataType.SHORT:
            vector.Add(tiffReader.ReadUInt16());
            break;
          case TagDataType.SSHORT:
            vector.Add(tiffReader.ReadInt16());
            break;
          case TagDataType.LONG:
            vector.Add(tiffReader.ReadUInt32());
            break;
          case TagDataType.SLONG:
            vector.Add(tiffReader.ReadInt32());
            break;
          case TagDataType.DOUBLE:
            vector.Add(tiffReader.ReadDouble());
            break;
          case TagDataType.FLOAT:
            vector.Add(tiffReader.ReadSingle());
            break;
          case TagDataType.RATIONAL:
            vector.Add(new Rational(tiffReader.ReadInt32(),
                                    tiffReader.ReadInt32()));
            break;
        }
      }
      //revert offset
      tiffReader.BaseStream.Seek(oldOffset, SeekOrigin.Begin);
      //buid string
      if (type == TagDataType.ASCII)
      //  vector.Add(sb.ToString());
        vector.Add(System.Text.ASCIIEncoding.ASCII.GetString(strArr));
    }
    else
    {
      if (type == TagDataType.ASCII)
        vector.Add(System.Text.ASCIIEncoding.ASCII.GetString(BitConverter.GetBytes(offset)));
      else
        vector.Add(offset);
    }

    _type = type;
    _count = count;
    _offset = offset;
    _tagData = vector;
    _ifd = ifd;
  }




  ///<summary>
  /// Creates a new tag with the given ID, type, number of objects / primitives stored in it
  /// and offset value.
  ///</summary>
  public Tag(ImageFileDirectory ifd, TagId id, TagDataType type, int count, int offset)
	{
		_id = id;
		_type = type;
		_count = count;
		if (count < 1)
		{
			throw new ArgumentException("Tiff tag count value must " +
				"not be smaller than 1: " + count);
		}
		_offset = offset;
    _tagData = null;
    _ifd = ifd;
	}




  public Tag(ImageFileDirectory ifd, TagId id, TagDataType type, int count, int offset, ArrayList tagData)
    : this(ifd, id, type, count, offset)
  {
    _tagData = tagData;
  }





  /// <summary>
  /// Returns the number of items stored in this tag.
  /// </summary>
	public int Count
	{
    get { return _count; }
	}





	/**
	 * Returns an item stored in this tag an <code>int</code> value.
	 * @param index zero-based index of the integer item to be returned
	 */
	public int getElementAsInt(int index)
	{
		Object element = getObject(index);

		if (element == null)
		{
			throw new ArgumentException("Tag does not contain a list of values.");
		}

		if (element.GetType() == typeof(short))
		{
			return (short)element;
		}
    if (element.GetType() == typeof(ushort))
    {
      return (ushort)element;
    }

		if (element.GetType() == typeof(int))
		{
      return (int)element;
		}

		if (element.GetType() == typeof(Byte))
		{
			return (Byte)element;
		}

		throw new ArgumentException("Element #" + index + " is not an integer value.");
	}




	/**
	 * Returns the ID of this tag, which may be one of the TAG_xyz constants.
	 */
  public TagId Id
	{
		get{return _id;}
	}





	/**
	 * Returns an object from this tag's Vector of items,
	 * or <code>null</code> if no such Vector exists.
	 */
	public Object getObject(int index)
	{
    if (_tagData == null)
		{
			return null;
		}
		else
		{
      return _tagData[index];
		}
	}




	/**
	 * Returns the offset value stored in this tag.
	 */
	public int Offset
	{
		get{return _offset;}
	}

	/**
	 * If this tag has a Vector of items and if the first item
	 * is a String, that String is returned, <code>null</code>
	 * otherwise.
	 */
	public String getString()
	{
    if (_tagData != null && _tagData.Count > 0)
		{
      object o = _tagData[0];
			if (o != null && o.GetType() == typeof(String))
			{
				return (String)o;
			}
		}
		return null;
	}

  /// <summary>
  ///  Returns the type of this tag's content as a TAG_TYPE_xyz constant.
  /// </summary>
 	public TagDataType Type
	{
    get { return _type; }
	}

	/**
	 * Returns/sets the Vector encapsulating the items stored in this tag.
   *
	 * If this tag encapsulates more than one item or a single
	 * item that does not fit into four bytes, this Vector
	 * will store all elements in it.
	 * The size() method called on that Vector object returns 
	 * the same value as getCount().
	 * @param vector the Vector with the items to be encapsulated by this tag
	 * @see #getVector
	 */
	public ArrayList TagData
	{
		get{return _tagData;}
    //set { _tagData = value; }
	}



  /// <summary>
  /// Returns if the value(s) stored in this tag are of type byte, short or
	/// long.
	/// Note that byte and short have the same meaning as in Java/C# (one and two bytes 
	/// large) while long is a 32-bit-value, just like <code>int</code> in Java/C#.
  /// </summary>
  /// <returns>true if this tag's contains integer values &lt;= 32 bits</returns>
	public bool isInt()
	{
    return (_type == TagDataType.BYTE || _type == TagDataType.SHORT || _type == TagDataType.LONG);
	}

  public override string ToString()
  {
    return _id.ToString();
  }


 }
}